package ca.scotthyndman.as2haxe.dom;

import java.util.ArrayList;
import java.util.List;

import ca.scotthyndman.as2haxe.dom.visitor.IVisitable;
import ca.scotthyndman.as2haxe.dom.visitor.IVisitor;

/**
 * Java compilation unit AST node type. This is the type of the root of an AST.
 * 
 * @author Scott Hyndman
 */
public class ASCompilationUnit extends ASTNode {

	/* --- Properties --- */

	/**
	 * The "imports" structural property of this node type.
	 */
	public static final ASChildListPropertyDescriptor IMPORTS_PROPERTY = new ASChildListPropertyDescriptor(
			ASCompilationUnit.class, "imports", ASImportDeclaration.class);

	/**
	 * The "package" structural property of this node type.
	 */
	public static final ASChildPropertyDescriptor PACKAGE_PROPERTY = new ASChildPropertyDescriptor(
			ASCompilationUnit.class, "package", ASPackageDeclaration.class,
			false);

	/**
	 * The "type" structural property of this node type.
	 */
	public static final ASChildPropertyDescriptor TYPE_PROPERTY = new ASChildPropertyDescriptor(
			ASCompilationUnit.class, "type", ASTypeDeclaration.class, false);

	/* --- Members --- */

	/** imports */
	private NodeList importList = new NodeList(IMPORTS_PROPERTY);

	/** type declaration */
	private ASTypeDeclaration type;

	/** package declaration */
	private ASPackageDeclaration packageDeclaration;

	/* --- Construction --- */

	/**
	 * Creates a new compilation unit.
	 */
	public ASCompilationUnit(AST ast) {
		super(ast);
	}

	/* --- Properties --- */

	/**
	 * Returns the node for the package declaration of this compilation unit, or
	 * <code>null</code> if this compilation unit is in the default package.
	 * 
	 * @return the package declaration node, or <code>null</code> if none
	 */
	public ASPackageDeclaration getPackageDeclaration() {
		return this.packageDeclaration;
	}

	/**
	 * Sets or clears the package declaration of this compilation unit node to
	 * the given package declaration node.
	 * 
	 * @param pkgDecl
	 *            the new package declaration node, or <code>null</code> if
	 *            this compilation unit does not have a package declaration
	 *            (that is in the default package)
	 * @exception IllegalArgumentException
	 *                if:
	 *                <ul>
	 *                <li>the node belongs to a different AST</li>
	 *                <li>the node already has a parent</li>
	 *                </ul>
	 */
	public void setPackageDeclaration(ASPackageDeclaration pkg) {
		ASTNode oldChild = this.packageDeclaration;
		preReplaceChild(oldChild, pkg, PACKAGE_PROPERTY);
		this.packageDeclaration = pkg;
		postReplaceChild(oldChild, pkg, PACKAGE_PROPERTY);
	}

	/**
	 * Returns the live list of nodes for the import declarations of this
	 * compilation unit, in order of appearance.
	 * 
	 * @return the live list of import declaration nodes (elementType:
	 *         <code>ASImportDeclaration</code>)
	 */
	public List<ASTNode> getImports() {
		return this.importList;
	}

	/**
	 * Returns the type declaration of this compilation unit.
	 * 
	 * @return the type declared in this compilation unit
	 */
	public ASTypeDeclaration getTypeDeclaration() {
		return this.type;
	}

	/**
	 * Sets or clears the type declaration of this compilation unit node to the
	 * given type declaration node.
	 * 
	 * @param type
	 *            the new type declaration node
	 */
	public void setTypeDeclaration(ASTypeDeclaration type) {
		ASTNode oldChild = this.type;
		preReplaceChild(oldChild, type, TYPE_PROPERTY);
		this.type = type;
		postReplaceChild(oldChild, type, TYPE_PROPERTY);
	}

	/* --- Internal --- */

	/*
	 * (omit javadoc for this method) Method declared on ASTNode.
	 */
	final List<ASTNode> internalGetChildListProperty(
			ASChildListPropertyDescriptor property) {
		if (property == IMPORTS_PROPERTY) {
			return getImports();
		}

		// allow default implementation to flag the error
		return super.internalGetChildListProperty(property);
	}

	/*
	 * (omit javadoc for this method) Method declared on ASTNode.
	 */
	final ASTNode internalGetSetChildProperty(
			ASChildPropertyDescriptor property, boolean get, ASTNode child) {
		if (property == PACKAGE_PROPERTY) {
			if (get) {
				return getPackageDeclaration();
			} else {
				setPackageDeclaration((ASPackageDeclaration) child);
				return null;
			}
		}
		if (property == TYPE_PROPERTY) {
			if (get) {
				return getTypeDeclaration();
			} else {
				setTypeDeclaration((ASTypeDeclaration) child);
				return null;
			}
		}
		// allow default implementation to flag the error
		return super.internalGetSetChildProperty(property, get, child);
	}

	/* --- Visitor --- */

	/**
	 * @see IVisitable#accept(IVisitor, boolean)
	 */
	public void accept(IVisitor as2Visitor, boolean recursive) {
		recursive = as2Visitor.visit(this) && recursive;
		if (recursive) {
			if (packageDeclaration != null) {
				packageDeclaration.accept(as2Visitor, recursive);
			}
			if (type != null) {
				type.accept(as2Visitor, recursive);
			}

			for (ASTNode imprt : importList) {
				((ASImportDeclaration) imprt).accept(as2Visitor, recursive);
			}
		}
	}

	/* --- Property definitions --- */

	/**
	 * A list of property descriptors (element type:
	 * {@link StructuralPropertyDescriptor}), or null if uninitialized.
	 */
	private static final List<ASPropertyDescriptor> PROPERTY_DESCRIPTORS;

	static {
		List<Object> propertyList = new ArrayList<Object>(3);
		createPropertyList(ASCompilationUnit.class, propertyList);
		addProperty(IMPORTS_PROPERTY, propertyList);
		addProperty(PACKAGE_PROPERTY, propertyList);
		addProperty(TYPE_PROPERTY, propertyList);
		PROPERTY_DESCRIPTORS = reapPropertyList(propertyList);
	}

	/**
	 * Returns a list of structural property descriptors for this node type.
	 * Clients must not modify the result.
	 * 
	 * @return a list of property descriptors (element type:
	 *         {@link ASPropertyDescriptor})
	 */
	public static List<ASPropertyDescriptor> propertyDescriptors() {
		return PROPERTY_DESCRIPTORS;
	}
}
